#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

namespace Server
{
    using namespace std;

    static const string defaultIp = "0.0.0.0"; //服务器端可接受任意IP地址传送的数据

    static const int gnum = 1024;

    enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};

    typedef function<void (string,uint16_t,string)> func_t;

    class udpServer
    {
    public:
        udpServer(const func_t &cb, const uint16_t &port, const string &ip = defaultIp)
        :_callback(cb)//回调函数
        , _port(port)
        , _ip(ip)
        , _sockfd(-1)
        {}
        void initServer()
        {
            // 1. 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);//返回值是文件描述符，第一个参数代表你是进行网络通信还是本地通信，AF_INET网络通信
            if(_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success: " << " : " << _sockfd << endl;

            // 2. 绑定port，ip(TODO)
            // 未来服务器要明确的port，不能随意改变
            struct sockaddr_in local; // 定义了一个变量，栈，用户，socketaddr_in 结构体是库中自带的结构体，也就是网络通信的结构体
            bzero(&local, sizeof(local));//bzero函数将对应结构体进行初始化为全0，按字节为单位向一段内存空间写0
            local.sin_family = AF_INET;//sin.family相当于你选择通信的方式，网络通信/本地通信
            local.sin_port = htons(_port); // 你如果要给别人发消息，你的port和ip要不要发送给对方 -- 要，sin_port相当于初始化端口号，
            //htons：因为在网络端，协议规定网络数据流都是大端字节序，而你不知道自己是大端还是小端，所以要进行转换，h:代表host，n：代表network，因为端口号是占2字节，所以s:代表转化成2字节
            local.sin_addr.s_addr = inet_addr(_ip.c_str());   // 1. string->uint32_t 2. htonl(); -> inet_addr
            //我们传递过来的ip是一个点分十进制的字符串，我们需要转换成整数，因为我们我们网络通信使用的是整数风格的ip。不需要我们自己去进行转换，直接用系统提供的函数
            //我们还需要将主机字节序转换成网络字节序，因为IP地址大小是4字节，所以还需要htonl()
            //所以我们使用inet_addr(const char* cp) -- 内部直接完成上述两件事情
            //local.sin_addr.s_addr = htonl(INADDR_ANY); // 任意地址bind，服务器的真实写法
            
            int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));//绑定
            if(n == -1)
            {
                cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
            // UDP Server 的预备工作完成
        }
        void start()
        {
            // 服务器的本质其实就是一个死循环
            char buffer[gnum];
            for(;;)
            {
                // 读取数据
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer); //必填
                ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);//recvfrom读取数据，0代表阻塞读取
                //最后两个参数是输入输出行参数，不用自己填，系统自己填，用来标识，返回对应的消息内容是从哪一个client发来的
                // 1. 数据是什么 2. 谁发的？
                if(s > 0)
                {
                    buffer[s] = 0;
                    string clientip = inet_ntoa(peer.sin_addr); //1. 网络序列->主机序列 2. int->点分十进制IP ，inet_ntoa(struct in_addr in)
                    uint16_t clientport = ntohs(peer.sin_port); // 网络序列->主机序列
                    string message = buffer;

                    cout << clientip <<"[" << clientport << "]# " << message << endl;
                    // 我们只把数据读上来就完了吗？对数据做处理
                    _callback(clientip, clientport, message);
                }
            }
        }
        ~udpServer()
        {
        }
    private:
        uint16_t _port;//端口号
        string _ip; // 实际上，一款网络服务器，不建议指明一个IP
        int _sockfd;
        func_t _callback; //回调
    };
}